package anivis.ui;

import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

import anivis.ui.map.MapView;

import com.bbn.openmap.LatLonPoint;
import com.bbn.openmap.Layer;
import com.bbn.openmap.LayerHandler;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.MapHandler;
import com.bbn.openmap.MouseDelegator;
import com.bbn.openmap.MultipleSoloMapComponentException;
import com.bbn.openmap.OMComponent;
import com.bbn.openmap.event.CoordMouseMode;
import com.bbn.openmap.gui.OMComponentPanel;
import com.bbn.openmap.gui.OMToolSet;
import com.bbn.openmap.gui.ToolPanel;

public class AniVisFrame extends JFrame {
    /** Properties */
    public static final String PROPERTY_ROOT = "anivis";
    public static final String PROPERTY_SEPARATOR = ".";
    public static final String PROPERTY_LAYERS = "layers";
    public static final String PROPERTY_PANEL_COMPONENTS = "panelcomponents";
    public static final String PROPERTY_MOUSE_MODES = "mousemodes";
    public static final String PROPERTY_COMPONENTS = "components";
    public static final String PROPERTY_MENUS = "menus";
    
    private final Controller controller;

    public AniVisFrame(Properties props, Controller controller)
    throws MultipleSoloMapComponentException, ClassNotFoundException, IOException {

        // Initialize the parent class (JFrame)
        super("AniVis 2008");
        
        this.controller = controller;

        // Use a Border layout manager
        getContentPane().setLayout(new BorderLayout());
        
        // add menus
        System.out.println("Adding menus...");
        JMenu[] menus = getMenus(props);
        if (menus.length > 0) {
            JMenuBar menuBar = new JMenuBar();
            for (int i = 0; i < menus.length; i++) {
                menuBar.add(menus[i]);
            }
            setJMenuBar(menuBar);
        }

        // Call quit when the window's close box is clicked.
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                quit();
            }
        });

        // the map bean will be the mapview itself...
        
        System.out.println("Creating MapHandler");
        // Create the BeanContext, known as the MapHandler.
        MapHandler mapHandler = new MapHandler();
        System.out.println("Creating MapBean");
        // Create a MapBean, and add it to the MapHandler.
        MapBean map = (MapBean)new MapView();

        // Set the map's center property...
        map.setCenter(new LatLonPoint(43.0f, -95.0f));
        // and scale
        map.setScale(80000000f);

        mapHandler.add(map);
        // Add the map to the JFrame
        getContentPane().add(map, BorderLayout.CENTER);

        System.out.println("Adding MouseEvent support...");
        // Add Mouse handling objects. The MouseDelegator manages the
        // MouseModes, controlling which one receives events from the
        // MapBean. The active MouseMode sends events to the layers
        // that want to receive events from it. The MouseDelegator
        // will find the MapBean in the MapHandler, and hook itself up
        // to it.
        mapHandler.add(new MouseDelegator());

        
        System.out.println("Adding generic components...");
        OMComponent[] components = getComponents(props);
        for (int i = 0; i < components.length; i++) {
            mapHandler.add(components[i]);
        }

        System.out.println("Creating ToolPanel...");
        // Add the standard panning and zoom GUI to the JFrame.
        // Create the tool...
        mapHandler.add(new OMToolSet());
        // Create the ToolPanel. It will find the OMToolSet in the
        // MapHandler.
        ToolPanel toolPanel = new ToolPanel();
        mapHandler.add(toolPanel);

        // Add the ToolPanel to the right place in this JFrame.
        getContentPane().add(toolPanel, BorderLayout.NORTH);

        System.out.println("Creating Layers...");
        Layer[] layers = getLayers(props);

        // Use the LayerHandler to manage all layers, whether they are
        // on the map or not. You can add a layer to the map by
        // setting layer.setVisible(true).
        LayerHandler layerHandler = new LayerHandler();
        for (int i = 0; i < layers.length; i++) {
            layers[i].setVisible(true);
            layerHandler.addLayer(layers[i]);
        }
        
        System.out.println("Adding panel components...");
        OMComponentPanel[] panelComponents = getPanelComponents(props);
        for (int i = 0; i < panelComponents.length; i++) {
            panelComponents[i].setVisible(true);
            mapHandler.add(panelComponents[i]);
        }
        
        System.out.println("Adding mouse modes...");
        CoordMouseMode[] modes = getMouseModes(props);
        for (int i = 0; i < modes.length; i++) {
            mapHandler.add(modes[i]);
        }
        
        mapHandler.add(layerHandler);
        System.out.println("Done creating...");
    }

    /**
     * Exits the application.
     */
    protected void quit() {
        System.exit(0);
    }
    
    private String getProperty(String propertyName, Properties p, boolean useRoot) {
        String fullName = (useRoot ? (PROPERTY_ROOT + PROPERTY_SEPARATOR) : "") + propertyName;
        String value = p.getProperty(fullName);
        if (value == null) {
            System.out.println("Property " + fullName + " not found!");
            value = "";
        }
        return value;
    }
    
    private StringTokenizer getProperties(String propertyName, Properties p, String separator, boolean useRoot) {
        String value = getProperty(propertyName, p, useRoot);
        return new StringTokenizer(value, separator);
    }
    
    /**
     * Returns an instance of a bean given by the property name.
     * Internally, the name of the property to be looked for resolves to "anivis." + propertyName + ".class"
     */
    private Object instantiate(String propertyName, Properties p) throws ClassNotFoundException, IOException {
        String classNameValue = getProperty(propertyName + PROPERTY_SEPARATOR + "class", p, false);
        System.out.println("Instantiating " + classNameValue);
        return java.beans.Beans.instantiate(null, classNameValue);
    }

    /**
     * Gets the names of the Layers to be loaded from the properties
     * passed in, initializes them, and returns them.
     * 
     * @param p the properties, among them the property represented by
     *        the String layersProperty above, which will tell us
     *        which Layers need to be loaded
     * @return an array of Layers ready to be added to the map bean
     * @see #layersProperty
     */
    private Layer[] getLayers(Properties p)  throws ClassNotFoundException, IOException {
        StringTokenizer tokens = getProperties(PROPERTY_LAYERS, p, " ", true);
        Layer[] layers = new Layer[tokens.countTokens()];
        // instantiate and set the properties
        for (int i = 0; i < layers.length; i++) {
            String layerName = tokens.nextToken();
            layers[i] = (Layer)instantiate(layerName, p);
            layers[i].setProperties(layerName, p);
        }
        return layers;
    }
    
    private OMComponentPanel[] getPanelComponents(Properties p)  throws ClassNotFoundException, IOException {
        StringTokenizer tokens = getProperties(PROPERTY_PANEL_COMPONENTS, p, " ", true);
        OMComponentPanel[] components = new OMComponentPanel[tokens.countTokens()];
        // instantiate and set the properties
        for (int i = 0; i < components.length; i++) {
            String layerName = tokens.nextToken();
            components[i] = (OMComponentPanel)instantiate(layerName, p);
            components[i].setProperties(layerName, p);
        }
        return components;
    }
    
    private CoordMouseMode[] getMouseModes(Properties p)  throws ClassNotFoundException, IOException {
        StringTokenizer tokens = getProperties(PROPERTY_MOUSE_MODES, p, " ", true);
        CoordMouseMode[] modes = new CoordMouseMode[tokens.countTokens()];
        // instantiate and set the properties
        for (int i = 0; i < modes.length; i++) {
            String layerName = tokens.nextToken();
            modes[i] = (CoordMouseMode)instantiate(layerName, p);
            modes[i].setProperties(layerName, p);
        }
        return modes;
    }
    
    private OMComponent[] getComponents(Properties p)  throws ClassNotFoundException, IOException {
        StringTokenizer tokens = getProperties(PROPERTY_COMPONENTS, p, " ", true);
        OMComponent[] components = new OMComponent[tokens.countTokens()];
        // instantiate and set the properties
        for (int i = 0; i < components.length; i++) {
            String layerName = tokens.nextToken();
            components[i] = (OMComponent)instantiate(layerName, p);
            components[i].setProperties(layerName, p);
        }
        return components;
    }
    
    private JMenu[] getMenus(Properties p) throws ClassNotFoundException, IOException {
        StringTokenizer tokens = getProperties(PROPERTY_MENUS, p, " ", true);
        JMenu[] components = new JMenu[tokens.countTokens()];
        // instantiate and set the properties
        for (int i = 0; i < components.length; i++) {
            String layerName = tokens.nextToken();
            components[i] = (JMenu)instantiate(layerName, p);
        }
        return components;
    }
}
